home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue69 / Alfresco / AACpndF.pas next >
Encoding:
Pascal/Delphi Source File  |  2001-04-04  |  32.6 KB  |  1,092 lines

  1. {*********************************************************}
  2. {* AACpndF                                               *}
  3. {* Copyright (c) Julian M Bucknall 2001                  *}
  4. {* All rights reserved.                                  *}
  5. {*********************************************************}
  6. {* Algorithms Alfresco: Compound file class              *}
  7. {*********************************************************}
  8.  
  9. {Note: this unit is released as freeware. In other words, you are free
  10.        to use this unit in your own applications, however I retain all
  11.        copyright to the code. JMB}
  12.  
  13. unit AACpndF;
  14.  
  15. {Notes: a compound file is a file that can contain other subfiles and
  16.         directories. It is a file system of its own. Microsoft name
  17.         this kind of functionality as "structured storage" and there's
  18.         a whole OLE/COM API for it.}
  19.  
  20. interface
  21.  
  22. uses
  23.   SysUtils,
  24.   Classes,
  25.   AAIntLst;
  26.  
  27. type
  28.   TaaHandle = pointer;
  29.  
  30.   TaaCFDirEntryType = (detUnused, detFolder, detSubfile);
  31.  
  32.   PaaCFDirEntry = ^TaaCFDirEntry;
  33.   TaaCFDirEntry = packed record     {a directory entry..}
  34.     deName     : string;            {..entry name}
  35.     deType     : TaaCFDirEntryType; {..type of entry}
  36.     de1stBlock : word;              {..first block of data}
  37.     deSize     : longint;           {..size of entry}
  38.     deTime     : TDateTime;         {..timestamp of last update}
  39.     deAttr     : longint;           {..attributes}
  40.   end;
  41.  
  42.   TaaWalkFolderAction = procedure (var aDirEntry : TaaCFDirEntry;
  43.                                    var aStopWalk : boolean);
  44.  
  45.   TaaCompoundFile = class
  46.     private
  47.       FCFSize      : integer;
  48.       FFAT         : TaaIntList;
  49.       FFATBlocks   : TaaIntList;
  50.       FHeader      : pointer;
  51.       FOpenFolders : TList;
  52.       FRoot        : TaaHandle;
  53.       FStream      : TFileStream;
  54.     protected
  55.       function cfGetRoot : TaaHandle;
  56.  
  57.       function cfAddBlock(var aBlock) : integer;
  58.       function cfGetEmptyBlock : integer;
  59.       function cfIsOpenFolder(aParent : TaaHandle;
  60.                         const aName   : string;
  61.                           var aHandle : TaaHandle) : boolean;
  62.       function cfIsValidFolder(aHandle : TaaHandle) : boolean;
  63.       procedure cfPrepare;
  64.       procedure cfReadBlock(aInx : integer; var aBlock);
  65.       procedure cfReadData(aStartInx : integer;
  66.                            aStream   : TStream;
  67.                            aLen      : integer);
  68.       procedure cfReadFAT;
  69.       procedure cfReadHeader;
  70.       procedure cfReleaseChain(aStartInx  : integer;
  71.                                aInclStart : boolean);
  72.       procedure cfSaveFolder(aHandle : TaaHandle);
  73.       procedure cfSaveRootFolder;
  74.       procedure cfWriteBlock(aInx : integer; var aBlock);
  75.       procedure cfWriteData(aStartInx : integer; aStream : TStream);
  76.       procedure cfWriteFAT;
  77.     public
  78.       constructor Create(const aFileName : string; aMode : word);
  79.       destructor Destroy; override;
  80.  
  81.       {folder methods}
  82.       function AddFolder(aParent : TaaHandle;
  83.                    const aName   : string) : TaaHandle;
  84.       procedure CloseFolder(aHandle : TaaHandle);
  85.       procedure DeleteFolder(aHandle : TaaHandle);
  86.       function OpenFolder(aParent : TaaHandle;
  87.                     const aName   : string) : TaaHandle;
  88.       procedure WalkFolder(aHandle : TaaHandle;
  89.                            aAction : TaaWalkFolderAction);
  90.  
  91.       {subfile methods}
  92.       procedure DeleteSubfile(aFolder : TaaHandle;
  93.                         const aName   : string);
  94.       procedure ReadSubfile(aFolder : TaaHandle;
  95.                       const aName   : string;
  96.                             aStream : TStream);
  97.       procedure UpdateSubfile(aFolder : TaaHandle;
  98.                         const aName   : string;
  99.                               aStream : TStream);
  100.  
  101.       property Root : TaaHandle read cfGetRoot;
  102.  
  103.   end;
  104.  
  105.   TaaSubfileStream = class(TStream)
  106.     private
  107.       FCF       : TaaCompoundFile;
  108.       FFolder   : TaaHandle;
  109.       FModified : boolean;
  110.       FName     : string;
  111.       FStream   : TMemoryStream;
  112.     protected
  113.     public
  114.       constructor Create(aCF     : TaaCompoundFile;
  115.                          aFolder : TaaHandle;
  116.                    const aName   : string;
  117.                          aCreate : boolean);
  118.       destructor Destroy; override;
  119.  
  120.       function Read(var Buffer; Count : longint) : longint; override;
  121.       function Write(const Buffer; Count : longint) : longint; override;
  122.       function Seek(Offset : longint; Origin : Word) : longint; override;
  123.   end;
  124.  
  125. implementation
  126.  
  127. const
  128.   CFSignature = $46434141;   {AACF: 1st 4 bytes of a compound file}
  129.   CFBlockSize = 512;         {fixed block size}
  130.   CFFATNodeEntryCount = CFBlockSize div sizeof(word);
  131.                              {number of FAT entries per block}
  132.   FATUnusedBlock = $FFFF;    {FAT entry for unused block}
  133.   FATEndOfChain = $FFFE;     {FAT entry for end of FAT chain}
  134.  
  135. type
  136.   PCFBlock = ^TCFBlock;
  137.   TCFBlock = array [0..pred(CFBlockSize)] of byte;
  138.  
  139.   PCFHeader = ^TCFHeader;
  140.   TCFHeader = packed record  {header record for compound file..}
  141.     cfhSignature : longint;  {..signature, equals AACF}
  142.     cfhBlockSize : longint;  {..should be 512}
  143.     cfhFATSize   : longint;  {..number of blocks in the FAT}
  144.     cfhRootSize  : longint;  {..size in bytes of the root}
  145.   end;
  146.  
  147.   PFATNode = ^TFATNode;
  148.   TFATNode = array [0..pred(CFFATNodeEntryCount)] of word;
  149.  
  150. {====================================================================}
  151. type
  152.   TCFFolder = class
  153.     private
  154.       FCount    : integer;
  155.       FList     : TList;
  156.       FModified : boolean;
  157.       FName     : string;
  158.       FParent   : TaaHandle;
  159.       FRefCount : integer;
  160.     protected
  161.       function cffGetCount : integer;
  162.       function cffGetDirEntry(aInx : integer) : PaaCFDirEntry;
  163.       procedure cffClear;
  164.     public
  165.       constructor Create(aParent : TaaHandle; const aName : string);
  166.       destructor Destroy; override;
  167.  
  168.       function AddDirEntry(const aName : string;
  169.                                  aType : TaaCFDirEntryType)
  170.                                                       : PaaCFDirEntry;
  171.       procedure RemoveDirEntry(aDE : PaaCFDirEntry);
  172.       function GetDirEntry(const aName : string;
  173.                                  aType : TaaCFDirEntryType)
  174.                                                       : PaaCFDirEntry;
  175.       procedure LoadFromStream(aStrm : TStream);
  176.       procedure SaveToStream(aStrm : TStream);
  177.  
  178.       procedure MarkModified;
  179.  
  180.       function DecRefCount : boolean;
  181.       procedure IncRefCount;
  182.  
  183.       property Count : integer read cffGetCount;
  184.       property DirEntry[aInx : integer] : PaaCFDirEntry
  185.                   read cffGetDirEntry;
  186.       property Modified : boolean read FModified;
  187.       property Name : string read FName;
  188.       property Parent : TaaHandle read FParent;
  189.   end;
  190. {--------}
  191. constructor TCFFolder.Create(aParent : TaaHandle;
  192.                        const aName : string);
  193. begin
  194.   inherited Create;
  195.   FParent := aParent;
  196.   FName := aName;
  197.   FList := TList.Create;
  198.   FRefCount := 1;
  199. end;
  200. {--------}
  201. destructor TCFFolder.Destroy;
  202. begin
  203.   if (FList <> nil) then begin
  204.     cffClear;
  205.     FList.Free;
  206.   end;
  207.   inherited Destroy;
  208. end;
  209. {--------}
  210. function TCFFolder.AddDirEntry(const aName : string;
  211.                                       aType : TaaCFDirEntryType)
  212.                                                       : PaaCFDirEntry;
  213. begin
  214.   Result := AllocMem(sizeof(TaaCFDirEntry));
  215.   Result.deName := aName;
  216.   Result.deType := aType;
  217.   FList.Add(Result);
  218.   MarkModified;
  219. end;
  220. {--------}
  221. procedure TCFFolder.cffClear;
  222. var
  223.   i     : integer;
  224.   Entry : PaaCFDirEntry;
  225. begin
  226.   for i := 0 to pred(FList.Count) do begin
  227.     Entry := FList.List^[i];
  228.     Entry.deName := '';
  229.     Dispose(Entry);
  230.   end;
  231.   FList.Clear;
  232.   FCount := 0;
  233. end;
  234. {--------}
  235. function TCFFolder.cffGetCount : integer;
  236. begin
  237.   Result := FList.Count;
  238. end;
  239. {--------}
  240. function TCFFolder.cffGetDirEntry(aInx : integer) : PaaCFDirEntry;
  241. begin
  242.   Assert((0 <= aInx) and (aInx < Count),
  243.          'TCFFolder.fGetDirEntry: index out of bounds');
  244.   Result := PaaCFDirEntry(FList.List^[aInx]);
  245. end;
  246. {--------}
  247. function TCFFolder.DecRefCount : boolean;
  248. begin
  249.   dec(FRefCount);
  250.   if (FRefCount > 0) then
  251.     Result := false
  252.   else begin
  253.     Result := true;
  254.     Free;
  255.   end;
  256. end;
  257. {--------}
  258. function TCFFolder.GetDirEntry(const aName : string;
  259.                                      aType : TaaCFDirEntryType)
  260.                                                       : PaaCFDirEntry;
  261. var
  262.   i : integer;
  263. begin
  264.   for i := 0 to pred(FList.Count) do begin
  265.     Result := PaaCFDirEntry(FList.List^[i]);
  266.     if (Result^.deType = aType) and (Result^.deName = aName) then
  267.       Exit;
  268.   end;
  269.   Result := nil;
  270. end;
  271. {--------}
  272. procedure TCFFolder.IncRefCount;
  273. begin
  274.   inc(FRefCount);
  275. end;
  276. {--------}
  277. procedure TCFFolder.LoadFromStream(aStrm : TStream);
  278. var
  279.   i       : integer;
  280.   Entry   : PaaCFDirEntry;
  281.   NameLen : byte;
  282.   CountInStrm : longint;
  283. begin
  284.   aStrm.Seek(0, soFromBeginning);
  285.   cffClear;
  286.   aStrm.ReadBuffer(CountInStrm, sizeof(longint));
  287.   for i := 0 to pred(CountInStrm) do begin
  288.     New(Entry);
  289.     with Entry^ do begin
  290.       aStrm.ReadBuffer(NameLen, sizeof(NameLen));
  291.       SetLength(deName, NameLen);
  292.       aStrm.ReadBuffer(deName[1], NameLen);
  293.       aStrm.ReadBuffer(deType, sizeof(deType));
  294.       aStrm.ReadBuffer(de1stBlock, sizeof(de1stBlock));
  295.       aStrm.ReadBuffer(deSize, sizeof(deSize));
  296.       aStrm.ReadBuffer(deTime, sizeof(deTime));
  297.       aStrm.ReadBuffer(deAttr, sizeof(deAttr));
  298.     end;
  299.     FList.Add(Entry);
  300.   end;
  301. end;
  302. {--------}
  303. procedure TCFFolder.MarkModified;
  304. begin
  305.   FModified := true;
  306. end;
  307. {--------}
  308. procedure TCFFolder.RemoveDirEntry(aDE : PaaCFDirEntry);
  309. begin
  310.   Dispose(aDE);
  311.   FList.Remove(aDE);
  312.   MarkModified;
  313. end;
  314. {--------}
  315. procedure TCFFolder.SaveToStream(aStrm : TStream);
  316. var
  317.   i       : integer;
  318.   Entry   : PaaCFDirEntry;
  319.   NameLen : byte;
  320.   CountInStrm : longint;
  321. begin
  322.   aStrm.Seek(0, soFromBeginning);
  323.   CountInStrm := Count;
  324.   aStrm.WriteBuffer(CountInStrm, sizeof(longint));
  325.   for i := 0 to pred(Count) do begin
  326.     Entry := PaaCFDirEntry(FList.List^[i]);
  327.     with Entry^ do begin
  328.       NameLen := length(deName);
  329.       aStrm.WriteBuffer(NameLen, sizeof(NameLen));
  330.       aStrm.WriteBuffer(deName[1], NameLen);
  331.       aStrm.WriteBuffer(deType, sizeof(deType));
  332.       aStrm.WriteBuffer(de1stBlock, sizeof(de1stBlock));
  333.       aStrm.WriteBuffer(deSize, sizeof(deSize));
  334.       aStrm.WriteBuffer(deTime, sizeof(deTime));
  335.       aStrm.WriteBuffer(deAttr, sizeof(deAttr));
  336.     end;
  337.   end;
  338. end;
  339. {====================================================================}
  340.  
  341.  
  342. {===TaaCompoundFile==================================================}
  343. constructor TaaCompoundFile.Create(const aFileName : string; aMode : word);
  344. begin
  345.   {create the ancestor}
  346.   inherited Create;
  347.  
  348.   {open the file stream}
  349.   FStream := TFileStream.Create(aFileName, aMode);
  350.  
  351.   {create the in-memory FAT}
  352.   FFAT := TaaIntList.Create;
  353.   FFATBlocks := TaaIntList.Create;
  354.  
  355.   {allocate the header}
  356.   GetMem(FHeader, CFBlockSize);
  357.  
  358.   {allocate the list of open folders}
  359.   FOpenFolders := TList.Create;
  360.  
  361.   {if the stream is new (size is zero) write the header record}
  362.   if (FStream.Size = 0) then
  363.     cfPrepare
  364.   {otherwise read the header and make sure that it's one of our files}
  365.   else
  366.     cfReadHeader;
  367. end;
  368. {--------}
  369. destructor TaaCompoundFile.Destroy;
  370. var
  371.   i : integer;
  372.   Folder : TCFFolder;
  373. begin
  374.   {destroy the open folders}
  375.   if (FOpenFolders <> nil) then begin
  376.     for i := pred(FOpenFolders.Count) downto 0 do begin
  377.       Folder := TCFFolder(FOpenFolders.List^[i]);
  378.       CloseFolder(Folder);
  379.     end;
  380.     FOpenFolders.Free;
  381.   end;
  382.  
  383.   {destroy the root if it was opened}
  384.   cfSaveRootFolder;
  385.   TCFFolder(FRoot).Free;
  386.  
  387.   {destroy the FAT}
  388.   cfWriteFAT;
  389.   FFATBlocks.Free;
  390.   FFAT.Free;
  391.  
  392.   {free the header block}
  393.   if (FHeader <> nil) then begin
  394.     cfWriteBlock(0, FHeader^);
  395.     FreeMem(FHeader, CFBlockSize);
  396.   end;
  397.  
  398.   {close the stream}
  399.   FStream.Free;
  400.  
  401.   {destroy the ancestor}
  402.   inherited Destroy;
  403. end;
  404. {--------}
  405. function TaaCompoundFile.AddFolder(aParent : TaaHandle;
  406.                              const aName   : string) : TaaHandle;
  407. var
  408.   DE     : PaaCFDirEntry;
  409.   Folder : TCFFolder;
  410. begin
  411.   {check that the parent is a valid folder}
  412.   if not cfIsValidFolder(aParent) then
  413.     raise Exception.Create(
  414.           'TaaCompoundFile.AddFolder: parent is not valid handle');
  415.  
  416.   {get the directory entry of the folder; if we succeed then the
  417.    folder already exists--an error}
  418.   DE := TCFFolder(aParent).GetDirEntry(aName, detFolder);
  419.   if (DE <> nil) then
  420.     raise Exception.Create(
  421.           'TaaCompoundFile.AddFolder: name already exists as valid folder');
  422.  
  423.   {create the folder}
  424.   Folder := TCFFolder.Create(aParent, aName);
  425.   Folder.MarkModified;
  426.  
  427.   {add the folder name to the parent's directory list}
  428.   TCFFolder(aParent).AddDirEntry(aName, detFolder);
  429.   TCFFolder(aParent).IncRefCount;
  430.  
  431.   {add the folder to the open folders list, return the folder}
  432.   FOpenFolders.Add(Folder);
  433.   Result := TaaHandle(Folder);
  434. end;
  435. {--------}
  436. function TaaCompoundFile.cfAddBlock(var aBlock) : integer;
  437. begin
  438.   Result := FCFSize div CFBlockSize;
  439.   cfWriteBlock(Result, aBlock);
  440. end;
  441. {--------}
  442. function TaaCompoundFile.cfGetEmptyBlock : integer;
  443. var
  444.   i, j    : integer;
  445.   FATInx  : integer;
  446.   FATNode : TFATNode;
  447. begin
  448.   {walk the FAT looking for an unused FAT entries}
  449.   i := 0;
  450.   while (i < FFAT.Count) and (FFAT[i] <> FATUnusedBlock) do
  451.     inc(i);
  452.   {if there were no unused FAT entries..}
  453.   if (i >= FFAT.Count) then begin
  454.     {we need to add another FAT node to the compound file}
  455.     FillChar(FATNode, CFBlockSize, $FF);
  456.     FATInx := cfAddBlock(FATNode);
  457.     FFAT.Capacity := FFAT.Capacity + CFFATNodeEntryCount;
  458.     for j := 0 to pred(CFFATNodeEntryCount) do
  459.       FFAT.Add(FATUnusedBlock);
  460.     FFAT[FFATBlocks.Last] := FATInx;
  461.     FFAT[FATInx] := FATEndOfChain;
  462.     FFATBlocks.Add(FATInx);
  463.     inc(PCFHeader(FHeader)^.cfhFATSize);
  464.     {we'll use the first unused FAT entry}
  465.     i := FFAT.Count - CFFATNodeEntryCount + 1;
  466.   end;
  467.   FFAT[i] := FATEndOfChain;
  468.   Result := i;
  469. end;
  470. {--------}
  471. function TaaCompoundFile.cfGetRoot : TaaHandle;
  472. var
  473.   Strm : TMemoryStream;
  474.   WorkRoot : TCFFolder;
  475. begin
  476.   if (FRoot = nil) then begin
  477.     WorkRoot := TCFFolder.Create(nil, '');
  478.     try
  479.       if (PCFHeader(FHeader)^.cfhRootSize <> 0) then begin
  480.         Strm := TMemoryStream.Create;
  481.         try
  482.           cfReadData(2, Strm, PCFHeader(FHeader)^.cfhRootSize);
  483.           WorkRoot.LoadFromStream(Strm);
  484.         finally
  485.           Strm.Free;
  486.         end;
  487.       end;
  488.     except
  489.       WorkRoot.Free;
  490.       raise;
  491.     end;
  492.     FRoot := WorkRoot;
  493.   end;
  494.   Result := FRoot;
  495. end;
  496. {--------}
  497. function TaaCompoundFile.cfIsOpenFolder(aParent : TaaHandle;
  498.                                   const aName   : string;
  499.                                     var aHandle : TaaHandle) : boolean;
  500. var
  501.   i : integer;
  502.   Folder : TCFFolder;
  503. begin
  504.   Assert(aParent <> nil,
  505.          'TaaCompoundFile.cfIsOpenFolder: should not be called with nil parent');
  506.   Result := false;
  507.   for i := 0 to pred(FOpenFolders.Count) do begin
  508.     Folder := TCFFolder(FOpenFolders.List^[i]);
  509.     if (Folder.Parent = aParent) and (Folder.Name = aName) then begin
  510.       Result := true;
  511.       aHandle := TaaHandle(Folder);
  512.       Exit;
  513.     end;
  514.   end;
  515. end;
  516. {--------}
  517. function TaaCompoundFile.cfIsValidFolder(aHandle : TaaHandle) : boolean;
  518. var
  519.   i : integer;
  520. begin
  521.   if (aHandle = nil) then
  522.     Result := false
  523.   else if (aHandle = FRoot) then
  524.     Result := true
  525.   else begin
  526.     Result := false;
  527.     for i := 0 to pred(FOpenFolders.Count) do
  528.       if (aHandle = FOpenFolders.List^[i]) then begin
  529.         Result := true;
  530.         Break;
  531.       end;
  532.   end;
  533. end;
  534. {--------}
  535. procedure TaaCompoundFile.cfPrepare;
  536. var
  537.   Header  : PCFHeader;
  538.   FATNode : TFATNode;
  539.   RootDir : TCFBlock;
  540. begin
  541.   {initialize the header (this will block 0)}
  542.   Header := FHeader;
  543.   FillChar(Header^, CFBlockSize, 0);
  544.   Header^.cfhSignature := CFSignature;
  545.   Header^.cfhBlockSize := 512;
  546.   Header^.cfhFATSize := 1;
  547.   {write out the header}
  548.   cfAddBlock(Header^);
  549.   {initialize the first FAT node (most entries are "unused")}
  550.   FillChar(FATNode, sizeof(FATNode), $FF);
  551.   FATNode[0] := FATEndOfChain;
  552.   FATNode[1] := FATEndOfChain;
  553.   FATNode[2] := FATEndOfChain;
  554.   {write out the first FAT node; set up the in-memory FAT}
  555.   cfAddBlock(FATNode);
  556.   cfReadFAT;
  557.   {initialize the root directory}
  558.   FillChar(RootDir, sizeof(RootDir), 0);
  559.   {write out the root directory}
  560.   cfAddBlock(RootDir);
  561. end;
  562. {--------}
  563. procedure TaaCompoundFile.cfReadBlock(aInx : integer; var aBlock);
  564. var
  565.   Offset : integer;
  566. begin
  567.   Offset := aInx * CFBlockSize;
  568.   Assert((0 <= Offset) and (Offset < FCFSize),
  569.          'TaaCompoundFile.cfReadBlock: Offset to read is out of range');
  570.   FStream.Seek(aInx * CFBlockSize, soFromBeginning);
  571.   FStream.ReadBuffer(aBlock, CFBlockSize);
  572. end;
  573. {--------}
  574. procedure TaaCompoundFile.cfReadData(aStartInx : integer;
  575.                                      aStream   : TStream;
  576.                                      aLen      : integer);
  577. var
  578.   Inx       : integer;
  579.   DataBlock : TCFBlock;
  580.   BytesToCopy : integer;
  581. begin
  582.   Assert(aLen <> 0,
  583.          'TaaCompoundFile.cfReadData: length of data is zero');
  584.   {position the stream at the start}
  585.   aStream.Seek(0, soFromBeginning);
  586.   {start at the first block}
  587.   Inx := aStartInx;
  588.   while (Inx <> FATEndOfChain) do begin
  589.     Assert(aLen <> 0,
  590.          'TaaCompoundFile.cfReadData: more data present than length indicates');
  591.     {read the current block}
  592.     cfReadBlock(Inx, DataBlock);
  593.     {write it to the stream}
  594.     if (aLen < CFBlockSize) then
  595.       BytesToCopy := aLen
  596.     else
  597.       BytesToCopy := CFBlockSize;
  598.     aStream.WriteBuffer(DataBlock, BytesToCopy);
  599.     dec(aLen, BytesToCopy);
  600.     {advance along to the next block}
  601.     Inx := FFAT[Inx];
  602.     Assert(Inx <> FATUnusedBlock,
  603.            'TaaCompoundFile.cfReadDir: unused block in FAT chain');
  604.   end;
  605.   Assert(aLen = 0,
  606.          'TaaCompoundFile.cfReadData: less data present than length indicates');
  607. end;
  608. {--------}
  609. procedure TaaCompoundFile.cfReadFAT;
  610. var
  611.   i       : integer;
  612.   Header  : PCFHeader;
  613.   FATNode : TFATNode;
  614.   FATInx  : integer;
  615. begin
  616.   {prepare the in-memory FAT}
  617.   Header := FHeader;
  618.   FFAT.Clear;
  619.   FFAT.Capacity := Header^.cfhFATSize * CFFATNodeEntryCount;
  620.   FFAT.IsSorted := false;
  621.   FFATBlocks.Clear;
  622.   FFAT.Capacity := Header^.cfhFATSize;
  623.   FFAT.IsSorted := false;
  624.   {the FAT starts at block 1}
  625.   FATInx := 1;
  626.   {read the FAT blocks}
  627.   while (FATInx <> FATEndOfChain) do begin
  628.     FFATBlocks.Add(FATInx);
  629.     cfReadBlock(FATInx, FATNode);
  630.     for i := 0 to pred(CFFATNodeEntryCount) do
  631.       FFAT.Add(FATNode[i]);
  632.     FATInx := FFAT[FATInx];
  633.     Assert(FATInx <> FATUnusedBlock,
  634.            'TaaCompoundFile.cfReadFAT: unused block in FAT chain');
  635.   end;
  636. end;
  637. {--------}
  638. procedure TaaCompoundFile.cfReadHeader;
  639. var
  640.   Header : PCFHeader;
  641. begin
  642.   {first test: check the stream size}
  643.   FCFSize := FStream.Size;
  644.   if (FCFSize < 3 * CFBlockSize) or
  645.      (((FCFSize div CFBlockSize) * CFBlockSize) <> FCFSize) then
  646.     raise Exception.Create('Stream is not a compound file: wrong size');
  647.  
  648.   {second test: check the first block is a compound file header}
  649.   Header := FHeader;
  650.   cfReadBlock(0, Header^);
  651.   if (Header^.cfhSignature <> CFSignature) or
  652.      (Header^.cfhBlockSize <> 512) or
  653.      (Header^.cfhFATSize <= 0) then
  654.     raise Exception.Create('Stream is not a compound file: header invalid');
  655.  
  656.   {now read the FAT}
  657.   cfReadFAT;
  658. end;
  659. {--------}
  660. procedure TaaCompoundFile.cfReleaseChain(aStartInx  : integer;
  661.                                          aInclStart : boolean);
  662. var
  663.   Inx     : integer;
  664.   NextInx : integer;
  665. begin
  666.   {depending on the user's request, begin at the start block or the
  667.    next one in the FAT chain}
  668.   if aInclStart then
  669.     Inx := aStartInx
  670.   else
  671.     Inx := FFAT[aStartInx];
  672.   {while we haven't reached the end of the chain}
  673.   while (Inx <> FATEndOfChain) do begin
  674.     {free this block}
  675.     NextInx := FFAT[Inx];
  676.     FFAT[Inx] := FATUnusedBlock;
  677.     Inx := NextInx;
  678.   end;
  679. end;
  680. {--------}
  681. procedure TaaCompoundFile.cfSaveFolder(aHandle : TaaHandle);
  682. var
  683.   Parent : TCFFolder;
  684.   Folder : TCFFolder;
  685.   DE     : PaaCFDirEntry;
  686.   Strm   : TMemoryStream;
  687. begin
  688.   {get the folder from the handle}
  689.   Folder := TCFFolder(aHandle);
  690.   {if the folder was modified...}
  691.   if Folder.Modified then begin
  692.     {get the parent handle}
  693.     Parent := TCFFolder(Folder.Parent);
  694.     {get the directory entry in the parent for this folder}
  695.     DE := Parent.GetDirEntry(Folder.Name, detFolder);
  696.     Assert(DE <> nil,
  697.            'TaaCompoundFile.cfSaveFolder: parent dir entry not found');
  698.     {if the folder is empty...}
  699.     if (Folder.Count = 0) then begin
  700.       {make sure it uses no blocks}
  701.       if (DE^.de1stBlock <> 0) then begin
  702.         cfReleaseChain(DE^.de1stBlock, true);
  703.         DE^.de1stBlock := 0;
  704.       end;
  705.       {update the parent}
  706.       DE^.deSize := 0;
  707.       DE^.deTime := Now;
  708.       Parent.MarkModified;
  709.     end
  710.     {otherwise the folder has directory entries}
  711.     else begin
  712.       {if this folder has never been written, get the first block}
  713.       if (DE^.de1stBlock = 0) then
  714.         DE^.de1stBlock := cfGetEmptyBlock;
  715.       {copy the folder data to a stream, and from thence to the
  716.        compound file}
  717.       Strm := TMemoryStream.Create;
  718.       try
  719.         {save the folder to the stream}
  720.         Folder.SaveToStream(Strm);
  721.         {save the stream to the compound file}
  722.         cfWriteData(DE^.de1stBlock, Strm);
  723.         {update the parent}
  724.         DE^.deSize := Strm.Size;
  725.         DE^.deTime := Now;
  726.         Parent.MarkModified;
  727.       finally
  728.         Strm.Free;
  729.       end;
  730.     end;
  731.   end;
  732. end;
  733. {--------}
  734. procedure TaaCompoundFile.cfSaveRootFolder;
  735. var
  736.   Folder : TCFFolder;
  737.   Strm   : TMemoryStream;
  738. begin
  739.   {get the root folder}
  740.   Folder := TCFFolder(FRoot);
  741.   {if the folder was modified...}
  742.   if (Folder <> nil) and Folder.Modified then begin
  743.     {copy the folder data to a stream, and from thence to the
  744.      compound file}
  745.     Strm := TMemoryStream.Create;
  746.     try
  747.       {save the folder to the stream}
  748.       Folder.SaveToStream(Strm);
  749.       {save the stream to the compound file}
  750.       cfWriteData(2, Strm);
  751.       {update the header}
  752.       PCFHeader(FHeader).cfhRootSize := Strm.Size;
  753.     finally
  754.       Strm.Free;
  755.     end;
  756.   end;
  757. end;
  758. {--------}
  759. procedure TaaCompoundFile.cfWriteBlock(aInx : integer; var aBlock);
  760. var
  761.   Offset : integer;
  762. begin
  763.   Offset := aInx * CFBlockSize;
  764.   Assert((0 <= Offset) and (Offset <= FCFSize),
  765.          'TaaCompoundFile.cfWriteBlock: Offset to write is out of range');
  766.   FStream.Seek(Offset, soFromBeginning);
  767.   FStream.WriteBuffer(aBlock, CFBlockSize);
  768.   if (Offset = FCFSize) then
  769.     FCFSize := Offset + CFBlockSize;
  770. end;
  771. {--------}
  772. procedure TaaCompoundFile.cfWriteData(aStartInx : integer;
  773.                                       aStream   : TStream);
  774. var
  775.   Inx       : integer;
  776.   NewInx    : integer;
  777.   DataBlock : TCFBlock;
  778.   BytesToGo : integer;
  779.   BytesTOCopy : integer;
  780. begin
  781.   {position the stream at the start}
  782.   aStream.Seek(0, soFromBeginning);
  783.   {start at the first block}
  784.   Inx := aStartInx;
  785.   {release all subsequent blocks}
  786.   cfReleaseChain(aStartInx, false);
  787.  
  788.   {calculate the number of bytes to write to the first block
  789.    (we don't have to allocate this one: it's already done)}
  790.   BytesToGo := aStream.Size;
  791.   if (BytesToGo > CFBlockSize) then
  792.     BytesToCopy := CFBlockSize
  793.   else begin
  794.     FillChar(DataBlock, sizeof(DataBlock), $CC);
  795.     BytesToCopy := BytesToGo;
  796.   end;
  797.   dec(BytesToGo, BytesToCopy);
  798.   {copy the data over for the first block}
  799.   aStream.ReadBuffer(DataBlock, BytesToCopy);
  800.   cfWriteBlock(Inx, DataBlock);
  801.  
  802.   {while there is still more data to write...}
  803.   while (BytesToGo <> 0) do begin
  804.   {calculate the number of bytes to write to the next block}
  805.     if (BytesToGo > CFBlockSize) then
  806.       BytesToCopy := CFBlockSize
  807.     else begin
  808.       FillChar(DataBlock, sizeof(DataBlock), $CC);
  809.       BytesToCopy := BytesToGo;
  810.     end;
  811.     dec(BytesToGo, BytesToCopy);
  812.     {allocate another block from the compound file}
  813.     NewInx := cfGetEmptyBlock;
  814.     FFAT[Inx] := NewInx;
  815.     Inx := NewInx;
  816.     {copy the data over}
  817.     aStream.ReadBuffer(DataBlock, BytesToCopy);
  818.     cfWriteBlock(Inx, DataBlock);
  819.   end;
  820. end;
  821. {--------}
  822. procedure TaaCompoundFile.cfWriteFAT;
  823. var
  824.   i, j    : integer;
  825.   FATNode : TFATNode;
  826.   BlockInx: integer;
  827. begin
  828.   Assert(FFATBlocks.Count * CFFATNodeEntryCount = FFAT.count,
  829.          'TaaCompoundFile.cfWriteFAT: invalid number of FAT entries');
  830.   i := 0;
  831.   for BlockInx := 0 to pred(FFATBlocks.Count) do begin
  832.     for j := 0 to pred(CFFATNodeEntryCount) do begin
  833.       FATNode[j] := FFAT[i];
  834.       inc(i);
  835.     end;
  836.     cfWriteBlock(FFATBlocks[BlockInx], FATNode);
  837.   end;
  838. end;
  839. {--------}
  840. procedure TaaCompoundFile.CloseFolder(aHandle : TaaHandle);
  841. var
  842.   i      : integer;
  843.   Folder : TCFFolder;
  844.   Parent : TCFFolder;
  845. begin
  846.   {Note: once opened, the root folder is never closed}
  847.  
  848.   {if the handle is not nil, nor the root...}
  849.   if (aHandle <> nil) and (aHandle <> FRoot) then
  850.     {find the folder in the open folders list...}
  851.     for i := 0 to pred(FOpenFolders.Count) do begin
  852.       Folder := TCFFolder(FOpenFolders.List^[i]);
  853.       {if the current item is the passed handle...}
  854.       if (aHandle = Folder) then begin
  855.         {get the parent}
  856.         Parent := TCFFolder(Folder.Parent);
  857.         {decrement the reference count for the open folder}
  858.         cfSaveFolder(Folder);
  859.         if Folder.DecRefCount then
  860.           FOpenFolders.Delete(i);
  861.         {decrement the reference count for the parent}
  862.         if (Parent <> FRoot) then begin
  863.           cfSaveFolder(Parent);
  864.           if Parent.DecRefCount then
  865.             FOpenFolders.Remove(Parent);
  866.         end;
  867.         Exit;
  868.       end;
  869.     end;
  870. end;
  871. {--------}
  872. procedure TaaCompoundFile.DeleteFolder(aHandle : TaaHandle);
  873. begin
  874.   Assert(false, 'TaaCompoundFile.DeleteFolder not implemented yet');
  875. end;
  876. {--------}
  877. procedure TaaCompoundFile.DeleteSubfile(aFolder : TaaHandle;
  878.                                   const aName   : string);
  879. var
  880.   DE : PaaCFDirEntry;
  881.   Folder   : TCFFolder;
  882. begin
  883.   {check that the folder is valid}
  884.   if not cfIsValidFolder(aFolder) then
  885.     raise Exception.Create(
  886.           'TaaCompoundFile.DeleteSubfile: parent is not valid handle');
  887.  
  888.   {get the directory entry of the subfile}
  889.   Folder := TCFFolder(aFolder);
  890.   DE := Folder.GetDirEntry(aName, detSubfile);
  891.  
  892.   {if the directory entry exists...}
  893.   if (DE <> nil) then begin
  894.     {free all the blocks occupied by the subfile}
  895.     if (DE^.de1stBlock <> 0) then
  896.       cfReleaseChain(DE^.de1stBlock, true);
  897.     {remove the directory entry}
  898.     Folder.RemoveDirEntry(DE);
  899.   end;
  900. end;
  901. {--------}
  902. function TaaCompoundFile.OpenFolder(aParent : TaaHandle;
  903.                               const aName   : string) : TaaHandle;
  904. var
  905.   DE     : PaaCFDirEntry;
  906.   Strm   : TMemoryStream;
  907.   Folder : TCFFolder;
  908.   Handle : TaaHandle;
  909. begin
  910.   {check that the parent is a valid folder}
  911.   if not cfIsValidFolder(aParent) then
  912.     raise Exception.Create(
  913.           'TaaCompoundFile.OpenFolder: parent is not valid handle');
  914.  
  915.   {get the directory entry of the folder; if this fails, the folder
  916.    name doesn't exist in the parent}
  917.   DE := TCFFolder(aParent).GetDirEntry(aName, detFolder);
  918.   if (DE = nil) then
  919.     raise Exception.Create(
  920.           'TaaCompoundFile.OpenFolder: name is not valid folder');
  921.  
  922.   {check to see if the folder hasn't already been opened; in which
  923.    case just increment the reference counts, return the open handle
  924.    and exit}
  925.   if cfIsOpenFolder(aParent, aName, Handle) then begin
  926.     TCFFolder(aParent).IncRefCount;
  927.     TCFFolder(Handle).IncRefCount;
  928.     Result := Handle;
  929.     Exit;
  930.   end;
  931.  
  932.   {create and read the folder}
  933.   Folder := TCFFolder.Create(aParent, aName);
  934.   try
  935.     if (DE^.deSize <> 0) then begin
  936.       Strm := TMemoryStream.Create;
  937.       try
  938.         cfReadData(DE^.de1stBlock, Strm, DE^.deSize);
  939.         Folder.LoadFromStream(Strm);
  940.       finally
  941.         Strm.Free;
  942.       end;
  943.     end;
  944.   except
  945.     Folder.Free;
  946.     raise;
  947.   end;
  948.  
  949.   {increment the reference count for the parent
  950.    (note: the folder has just been created so we don't update its
  951.           reference count)}
  952.   TCFFolder(aParent).IncRefCount;
  953.  
  954.   {add the folder to the open folders list, return the folder}
  955.   FOpenFolders.Add(Folder);
  956.   Result := TaaHandle(Folder);
  957. end;
  958. {--------}
  959. procedure TaaCompoundFile.ReadSubfile(aFolder : TaaHandle;
  960.                                 const aName   : string;
  961.                                       aStream : TStream);
  962. var
  963.   DE : PaaCFDirEntry;
  964. begin
  965.   {check that the folder is valid}
  966.   if not cfIsValidFolder(aFolder) then
  967.     raise Exception.Create(
  968.           'TaaCompoundFile.ReadSubfile: parent is not valid handle');
  969.  
  970.   {get the directory entry of the subfile; if this fails, the subfile
  971.    name doesn't exist in the folder}
  972.   DE := TCFFolder(aFolder).GetDirEntry(aName, detSubfile);
  973.   if (DE = nil) then
  974.     raise Exception.Create(
  975.           'TaaCompoundFile.ReadSubfile: name is not valid subfile');
  976.  
  977.   {if there's some data, copy it to the stream}
  978.   aStream.Seek(0, soFromBeginning);
  979.   if (DE^.deSize <> 0) then
  980.     cfReadData(DE^.de1stBlock, aStream, DE^.deSize);
  981.   aStream.Size := DE^.deSize;
  982. end;
  983. {--------}
  984. procedure TaaCompoundFile.UpdateSubfile(aFolder : TaaHandle;
  985.                                   const aName   : string;
  986.                                         aStream : TStream);
  987. var
  988.   DE : PaaCFDirEntry;
  989.   StrmSize : integer;
  990.   Folder   : TCFFolder;
  991. begin
  992.   {check that the folder is valid}
  993.   if not cfIsValidFolder(aFolder) then
  994.     raise Exception.Create(
  995.           'TaaCompoundFile.UpdateSubfile: parent is not valid handle');
  996.  
  997.   {get the directory entry of the subfile}
  998.   Folder := TCFFolder(aFolder);
  999.   DE := Folder.GetDirEntry(aName, detSubfile);
  1000.  
  1001.   {if the directory entry doesn't exist, create a new one}
  1002.   if (DE = nil) then
  1003.     DE := Folder.AddDirEntry(aName, detSubfile);
  1004.  
  1005.   {if the stream is empty, make sure the existing blocks are freed}
  1006.   StrmSize := aStream.Size;
  1007.   if (StrmSize = 0) then begin
  1008.     if (DE^.de1stBlock <> 0) then begin
  1009.       cfReleaseChain(DE^.de1stBlock, true);
  1010.       DE^.de1stBlock := 0;
  1011.     end;
  1012.   end
  1013.   {otherwise there's some data to write}
  1014.   else begin
  1015.     {if this subfile has never been written, get the first block}
  1016.     if (DE^.de1stBlock = 0) then
  1017.       DE^.de1stBlock := cfGetEmptyBlock;
  1018.     {save the stream to the compound file}
  1019.     cfWriteData(DE^.de1stBlock, aStream);
  1020.   end;
  1021.   {update the folder}
  1022.   DE^.deSize := StrmSize;
  1023.   DE^.deTime := Now;
  1024.   Folder.MarkModified;
  1025. end;
  1026. {--------}
  1027. procedure TaaCompoundFile.WalkFolder(aHandle : TaaHandle;
  1028.                                      aAction : TaaWalkFolderAction);
  1029. var
  1030.   i : integer;
  1031.   StopNow : boolean;
  1032.   Folder  : TCFFolder;
  1033. begin
  1034.   if not cfIsValidFolder(aHandle) then
  1035.     raise Exception.Create(
  1036.           'TaaCompoundFile.WalkFolder: invalid folder handle');
  1037.  
  1038.   StopNow := false;
  1039.   Folder := TCFFolder(aHandle);
  1040.   for i := 0 to pred(Folder.Count) do begin
  1041.     aAction(Folder.DirEntry[i]^, StopNow);
  1042.     if StopNow then
  1043.       Break;
  1044.   end;
  1045. end;
  1046. {====================================================================}
  1047.  
  1048.  
  1049. {===TaaSubfileStream=================================================}
  1050. constructor TaaSubfileStream.Create(aCF     : TaaCompoundFile;
  1051.                                     aFolder : TaaHandle;
  1052.                               const aName   : string;
  1053.                                     aCreate : boolean);
  1054. begin
  1055.   inherited Create;
  1056.   FCF := aCF;
  1057.   FFolder := aFolder;
  1058.   FName := aName;
  1059.   FStream := TMemoryStream.Create;
  1060.   if aCreate then
  1061.     aCF.DeleteSubfile(aFolder, aName)
  1062.   else
  1063.     aCF.ReadSubFile(aFolder, aName, FStream);
  1064. end;
  1065. {--------}
  1066. destructor TaaSubfileStream.Destroy;
  1067. begin
  1068.   if FModified then
  1069.     FCF.UpdateSubfile(FFolder, FName, FStream);
  1070.   FStream.Free;
  1071.   inherited Destroy;
  1072. end;
  1073. {--------}
  1074. function TaaSubfileStream.Read(var Buffer; Count : longint) : longint;
  1075. begin
  1076.   Result := FStream.Read(Buffer, Count);
  1077. end;
  1078. {--------}
  1079. function TaaSubfileStream.Write(const Buffer; Count : longint) : longint;
  1080. begin
  1081.   Result := FStream.Write(Buffer, Count);
  1082.   FModified := true;
  1083. end;
  1084. {--------}
  1085. function TaaSubfileStream.Seek(Offset : longint; Origin : Word) : longint;
  1086. begin
  1087.   Result := FStream.Seek(Offset, Origin);
  1088. end;
  1089. {====================================================================}
  1090.  
  1091. end.
  1092.